package com.baidu.iit.pxp.entity;

import com.baidu.iit.pvm.*;
import com.baidu.iit.pvm.delegate.ActivityExecution;
import com.baidu.iit.pvm.delegate.ExecutionListenerExecution;
import com.baidu.iit.pvm.delegate.SignallableActivityBehavior;
import com.baidu.iit.pvm.process.ActivityImpl;
import com.baidu.iit.pvm.process.ProcessDefinitionImpl;
import com.baidu.iit.pvm.process.ScopeImpl;
import com.baidu.iit.pvm.process.TransitionImpl;
import com.baidu.iit.pvm.runtime.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * User: huangweili
 * Date: 14-4-24
 * Time: 下午9:13
 */

public class ExecutionEntity extends VariableScopeImpl implements ActivityExecution, ExecutionListenerExecution, Execution, PvmExecution, ProcessInstance, InterpretableExecution {


    private static Logger log = LoggerFactory.getLogger(ExecutionEntity.class);

    protected static final int EVENT_SUBSCRIPTIONS_STATE_BIT = 1;
    protected static final int TASKS_STATE_BIT = 2;
    protected static final int JOBS_STATE_BIT = 3;


    protected ProcessDefinitionImpl processDefinition;

    protected ActivityImpl activity;

    protected TransitionImpl transition = null;

    protected TransitionImpl transitionBeingTaken = null;

    protected ExecutionEntity processInstance;

    protected ExecutionEntity parent;

    protected List<ExecutionEntity> executions;

    protected ExecutionEntity superExecution;

    protected ExecutionEntity subProcessInstance;

    protected StartingExecution startingExecution;

    protected boolean isActive = true;
    protected boolean isScope = true;
    protected boolean isConcurrent = false;
    protected boolean isEnded = false;
    protected boolean isEventScope = false;


    protected String eventName;
    protected PvmProcessElement eventSource;
    protected int executionListenerIndex = 0;


    protected List<TaskEntity> tasks;


    protected int cachedEntityState;

    protected boolean deleteRoot;
    protected String deleteReason;

    protected ExecutionEntity replacedBy;


    protected AtomicOperation nextOperation;
    protected boolean isOperating = false;

    protected int revision = 1;
    protected int suspensionState = SuspensionState.ACTIVE.getStateCode();


    protected String processDefinitionId;


    protected String activityId;


    protected String activityName;


    protected String processInstanceId;


    protected String businessKey;


    protected String parentId;


    protected String superExecutionId;

    protected boolean forcedUpdate;

    protected List<VariableInstanceEntity> queryVariables;

    public ExecutionEntity() {
    }

    public ExecutionEntity(ActivityImpl activityImpl) {
        this.startingExecution = new StartingExecution(activityImpl);
    }

    public ExecutionEntity createExecution() {

        ExecutionEntity createdExecution = newExecution();
        ensureExecutionsInitialized();
        executions.add(createdExecution);
        createdExecution.setParent(this);

        createdExecution.setProcessDefinition(getProcessDefinition());
        createdExecution.setProcessInstance(getProcessInstance());
        createdExecution.setActivity(getActivity());

        if (log.isDebugEnabled()) {
            log.debug("Child execution {} created with parent ", createdExecution, this);
        }

        return createdExecution;
    }

    public PvmProcessInstance createSubProcessInstance(PvmProcessDefinition processDefinition) {
        ExecutionEntity subProcessInstance = newExecution();

        subProcessInstance.setSuperExecution(this);
        this.setSubProcessInstance(subProcessInstance);

        subProcessInstance.setProcessDefinition((ProcessDefinitionImpl) processDefinition);
        subProcessInstance.setProcessInstance(subProcessInstance);

        return subProcessInstance;
    }

    protected ExecutionEntity newExecution() {
        ExecutionEntity newExecution = new ExecutionEntity();
        newExecution.executions = new ArrayList<ExecutionEntity>();
        return newExecution;
    }

    public void initialize() {
        log.debug("initializing {}", this);

        ScopeImpl scope = getScopeObject();
        ensureParentInitialized();

        variableInstances = new HashMap<String, VariableInstanceEntity>();
        variableInstanceList = new ArrayList<VariableInstanceEntity>();

        tasks = new ArrayList<TaskEntity>();

        cachedEntityState = 0;
    }

    public void start() {
        if (startingExecution == null && isProcessInstanceType()) {
            startingExecution = new StartingExecution(processDefinition.getInitial());
        }
        performOperation(AtomicOperation.PROCESS_START);
    }

    public void destroy() {
        if (log.isDebugEnabled()) {
            log.debug("destroying {}", this);
        }
        ensureParentInitialized();
        deleteVariablesInstanceForLeavingScope();

        setScope(false);
    }

    public void end() {
        isActive = false;
        isEnded = true;
        performOperation(AtomicOperation.ACTIVITY_END);
    }


    public void signal(String signalName, Object signalData) {
        ensureActivityInitialized();
        SignallableActivityBehavior activityBehavior = (SignallableActivityBehavior) activity.getActivityBehavior();

        try {
            activityBehavior.signal(this, signalName, signalData);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new PvmException("couldn't process signal '" + signalName + "' on activity '" + activity.getId() + "': " + e.getMessage(), e);
        }
    }

    public void take(PvmTransition transition) {
        if (this.transition != null) {
            throw new PvmException("already taking a transition");
        }
        if (transition == null) {
            throw new PvmException("transition is null");
        }
        setActivity((ActivityImpl) transition.getSource());
        setTransition((TransitionImpl) transition);
        performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_END);
    }

    public void executeActivity(PvmActivity activity) {
        setActivity((ActivityImpl) activity);
        performOperation(AtomicOperation.ACTIVITY_START);
    }

    public List<ActivityExecution> findInactiveConcurrentExecutions(PvmActivity activity) {
        List<ActivityExecution> inactiveConcurrentExecutionsInActivity = new ArrayList<ActivityExecution>();
        List<ActivityExecution> otherConcurrentExecutions = new ArrayList<ActivityExecution>();
        if (isConcurrent()) {
            List<? extends ActivityExecution> concurrentExecutions = getParent().getAllChildExecutions();
            for (ActivityExecution concurrentExecution : concurrentExecutions) {
                if (concurrentExecution.getActivity() == activity) {
                    if (!concurrentExecution.isActive()) {
                        inactiveConcurrentExecutionsInActivity.add(concurrentExecution);
                    }
                } else {
                    otherConcurrentExecutions.add(concurrentExecution);
                }
            }
        } else {
            if (!isActive()) {
                inactiveConcurrentExecutionsInActivity.add(this);
            } else {
                otherConcurrentExecutions.add(this);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("inactive concurrent executions in '{}': {}", activity, inactiveConcurrentExecutionsInActivity);
            log.debug("other concurrent executions: {}", otherConcurrentExecutions);
        }
        return inactiveConcurrentExecutionsInActivity;
    }

    protected List<ExecutionEntity> getAllChildExecutions() {
        List<ExecutionEntity> childExecutions = new ArrayList<ExecutionEntity>();
        for (ExecutionEntity childExecution : getExecutions()) {
            childExecutions.add(childExecution);
            childExecutions.addAll(childExecution.getAllChildExecutions());
        }
        return childExecutions;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public void takeAll(List<PvmTransition> transitions, List<ActivityExecution> recyclableExecutions) {
        transitions = new ArrayList<PvmTransition>(transitions);
        recyclableExecutions = (recyclableExecutions != null ? new ArrayList<ActivityExecution>(recyclableExecutions) : new ArrayList<ActivityExecution>());

        if (recyclableExecutions.size() > 1) {
            for (ActivityExecution recyclableExecution : recyclableExecutions) {
                if (((ExecutionEntity) recyclableExecution).isScope()) {
                    throw new PvmException("joining scope executions is not allowed");
                }
            }
        }

        ExecutionEntity concurrentRoot = ((isConcurrent && !isScope) ? getParent() : this);
        List<ExecutionEntity> concurrentActiveExecutions = new ArrayList<ExecutionEntity>();
        List<ExecutionEntity> concurrentInActiveExecutions = new ArrayList<ExecutionEntity>();
        for (ExecutionEntity execution : concurrentRoot.getExecutions()) {
            if (execution.isActive()) {
                concurrentActiveExecutions.add(execution);
            } else {
                concurrentInActiveExecutions.add(execution);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("transitions to take concurrent: {}", transitions);
            log.debug("active concurrent executions: {}", concurrentActiveExecutions);
        }

        if ((transitions.size() == 1)
                && (concurrentActiveExecutions.isEmpty())
                && allExecutionsInSameActivity(concurrentInActiveExecutions)
                ) {

            List<ExecutionEntity> recyclableExecutionImpls = (List) recyclableExecutions;
            recyclableExecutions.remove(concurrentRoot);
            for (ExecutionEntity prunedExecution : recyclableExecutionImpls) {
                // End the pruned executions if necessary.
                // Some recyclable executions are inactivated (joined executions)
                // Others are already ended (end activities)

                log.debug("pruning execution {}", prunedExecution);
                prunedExecution.remove();
            }

            log.debug("activating the concurrent root {} as the single path of execution going forward", concurrentRoot);
            concurrentRoot.setActive(true);
            concurrentRoot.setActivity(activity);
            concurrentRoot.setConcurrent(false);
            concurrentRoot.take(transitions.get(0));

        } else {

            List<OutgoingExecution> outgoingExecutions = new ArrayList<OutgoingExecution>();

            recyclableExecutions.remove(concurrentRoot);

            log.debug("recyclable executions for reuse: {}", recyclableExecutions);

            // first create the concurrent executions
            while (!transitions.isEmpty()) {
                PvmTransition outgoingTransition = transitions.remove(0);

                ExecutionEntity outgoingExecution = null;
                if (recyclableExecutions.isEmpty()) {
                    outgoingExecution = concurrentRoot.createExecution();
                    log.debug("new {} with parent {} created to take transition {}",
                            outgoingExecution, outgoingExecution.getParent(), outgoingTransition);
                } else {
                    outgoingExecution = (ExecutionEntity) recyclableExecutions.remove(0);
                    log.debug("recycled {} to take transition {}", outgoingExecution, outgoingTransition);
                }

                outgoingExecution.setActive(true);
                outgoingExecution.setScope(false);
                outgoingExecution.setConcurrent(true);
                outgoingExecution.setTransitionBeingTaken((TransitionImpl) outgoingTransition);
                outgoingExecutions.add(new OutgoingExecution(outgoingExecution, outgoingTransition, true));
            }

            // prune the executions that are not recycled
            for (ActivityExecution prunedExecution : recyclableExecutions) {
                log.debug("pruning execution {}", prunedExecution);
                prunedExecution.end();
            }

            // then launch all the concurrent executions
            for (OutgoingExecution outgoingExecution : outgoingExecutions) {
                outgoingExecution.take();
            }
        }
    }

    protected boolean allExecutionsInSameActivity(List<ExecutionEntity> executions) {
        if (executions.size() > 1) {
            String activityId = executions.get(0).getActivityId();
            for (ExecutionEntity execution : executions) {
                String otherActivityId = execution.getActivityId();
                if (!execution.isEnded) {
                    if ((activityId == null && otherActivityId != null)
                            || (activityId != null && otherActivityId == null)
                            || (activityId != null && otherActivityId != null && !otherActivityId.equals(activityId))) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    public void performOperation(AtomicOperation executionOperation) {
            performOperationSync(executionOperation);

    }

    protected void performOperationSync(AtomicOperation executionOperation) {


    }


    public boolean isActive(String activityId) {
        return findExecution(activityId) != null;
    }

    public void inactivate() {
        this.isActive = false;
    }

    // executions ///////////////////////////////////////////////////////////////

    /**
     * ensures initialization and returns the non-null executions list
     */
    public List<ExecutionEntity> getExecutions() {
        ensureExecutionsInitialized();
        return executions;
    }

    protected void ensureExecutionsInitialized() {
        if (executions == null) {

        }
    }

    public void setExecutions(List<ExecutionEntity> executions) {
        this.executions = executions;
    }

    /**
     * searches for an execution positioned in the given activity
     */
    public ExecutionEntity findExecution(String activityId) {
        if ((getActivity() != null)
                && (getActivity().getId().equals(activityId))
                ) {
            return this;
        }
        for (ExecutionEntity nestedExecution : getExecutions()) {
            ExecutionEntity result = nestedExecution.findExecution(activityId);
            if (result != null) {
                return result;
            }
        }
        return null;
    }

    public List<String> findActiveActivityIds() {
        List<String> activeActivityIds = new ArrayList<String>();
        collectActiveActivityIds(activeActivityIds);
        return activeActivityIds;
    }

    protected void collectActiveActivityIds(List<String> activeActivityIds) {
        ensureActivityInitialized();
        if (isActive && activity != null) {
            activeActivityIds.add(activity.getId());
        }
        ensureExecutionsInitialized();
        for (ExecutionEntity execution : executions) {
            execution.collectActiveActivityIds(activeActivityIds);
        }
    }


    public String getBusinessKey() {
        return businessKey;
    }

    public void setBusinessKey(String businessKey) {
        this.businessKey = businessKey;
    }

    public String getProcessBusinessKey() {
        return getProcessInstance().getBusinessKey();
    }

    public ProcessDefinitionImpl getProcessDefinition() {
        ensureProcessDefinitionInitialized();
        return processDefinition;
    }

    public void setProcessDefinitionId(String processDefinitionId) {
        this.processDefinitionId = processDefinitionId;
    }

    public String getProcessDefinitionId() {
        return processDefinitionId;
    }

    protected void ensureProcessDefinitionInitialized() {
        if ((processDefinition == null) && (processDefinitionId != null)) {

        }
    }

    public void setProcessDefinition(ProcessDefinitionImpl processDefinition) {
        this.processDefinition = processDefinition;
        this.processDefinitionId = processDefinition.getId();
    }

    public ExecutionEntity getProcessInstance() {
        ensureProcessInstanceInitialized();
        return processInstance;
    }

    protected void ensureProcessInstanceInitialized() {
    }

    public void setProcessInstance(InterpretableExecution processInstance) {
        this.processInstance = (ExecutionEntity) processInstance;
        if (processInstance != null) {
            this.processInstanceId = this.processInstance.getId();
        }
    }

    public boolean isProcessInstanceType() {
        return parentId == null;
    }

    public ActivityImpl getActivity() {
        ensureActivityInitialized();
        return activity;
    }

    protected void ensureActivityInitialized() {
        if ((activity == null) && (activityId != null)) {
            activity = getProcessDefinition().findActivity(activityId);
        }
    }

    public void setActivity(ActivityImpl activity) {
        this.activity = activity;
        if (activity != null) {
            this.activityId = activity.getId();
            this.activityName = (String) activity.getProperty("name");
        } else {
            this.activityId = null;
            this.activityName = null;
        }
    }

    public ExecutionEntity getParent() {
        ensureParentInitialized();
        return parent;
    }

    protected void ensureParentInitialized() {

    }

    public void setParent(InterpretableExecution parent) {
        this.parent = (ExecutionEntity) parent;

        if (parent != null) {
            this.parentId = ((ExecutionEntity) parent).getId();
        } else {
            this.parentId = null;
        }
    }

    public String getSuperExecutionId() {
        return superExecutionId;
    }

    public ExecutionEntity getSuperExecution() {
        ensureSuperExecutionInitialized();
        return superExecution;
    }

    public void setSuperExecution(ExecutionEntity superExecution) {
        this.superExecution = superExecution;
        if (superExecution != null) {
            superExecution.setSubProcessInstance(null);
        }

        if (superExecution != null) {
            this.superExecutionId = ((ExecutionEntity) superExecution).getId();
        } else {
            this.superExecutionId = null;
        }
    }

    protected void ensureSuperExecutionInitialized() {
        if (superExecution == null && superExecutionId != null) {

        }
    }

    public ExecutionEntity getSubProcessInstance() {
        ensureSubProcessInstanceInitialized();
        return subProcessInstance;
    }

    public void setSubProcessInstance(InterpretableExecution subProcessInstance) {
        this.subProcessInstance = (ExecutionEntity) subProcessInstance;
    }

    protected void ensureSubProcessInstanceInitialized() {

    }


    protected ScopeImpl getScopeObject() {
        ScopeImpl scope = null;
        if (isProcessInstanceType()) {
            scope = getProcessDefinition();
        } else {
            scope = getActivity();
        }
        return scope;
    }

    public boolean isScope() {
        return isScope;
    }

    public void setScope(boolean isScope) {
        this.isScope = isScope;
    }


    public void remove() {
        ensureParentInitialized();
        if (parent != null) {
            parent.ensureExecutionsInitialized();
            parent.executions.remove(this);
        }

        ensureVariableInstancesInitialized();
        deleteVariablesInstanceForLeavingScope();
        removeEventScopes();


    }

    public void destroyScope(String reason) {

        if (log.isDebugEnabled()) {
            log.debug("performing destroy scope behavior for execution {}", this);
        }

        List<InterpretableExecution> executions = new ArrayList<InterpretableExecution>(getExecutions());
        for (InterpretableExecution childExecution : executions) {
            if (childExecution.getSubProcessInstance() != null) {
                childExecution.getSubProcessInstance().deleteCascade(reason);
            }
            childExecution.deleteCascade(reason);
        }
    }

    private void removeEventScopes() {
        List<InterpretableExecution> childExecutions = new ArrayList<InterpretableExecution>(getExecutions());
        for (InterpretableExecution childExecution : childExecutions) {
            if (childExecution.isEventScope()) {
                log.debug("removing eventScope {}", childExecution);
                childExecution.destroy();
                childExecution.remove();
            }
        }
    }


    public ExecutionEntity getReplacedBy() {
        return replacedBy;
    }

    @Override
    public void setReplacedBy(InterpretableExecution replacedBy) {
        //TODO
    }


    @Override
    protected void initializeVariableInstanceBackPointer(VariableInstanceEntity variableInstance) {
        variableInstance.setProcessInstanceId(processInstanceId);
        variableInstance.setExecutionId(id);
    }

    @Override
    protected List<VariableInstanceEntity> loadVariableInstances() {
        return null;  //TODO
    }

    @Override
    protected VariableScopeImpl getParentVariableScope() {
        return getParent();
    }

    protected ExecutionEntity getSourceActivityExecution() {
        return (activityId != null ? this : null);
    }

    @Override
    protected boolean isActivityIdUsedForDetails() {
        return true;
    }

    @Override
    protected VariableInstanceEntity createVariableInstance(String variableName, Object value,
                                                            ExecutionEntity sourceActivityExecution) {
        VariableInstanceEntity result = super.createVariableInstance(variableName, value, sourceActivityExecution);
        return result;
    }

    @Override
    protected void updateVariableInstance(VariableInstanceEntity variableInstance, Object value,
                                          ExecutionEntity sourceActivityExecution) {
        super.updateVariableInstance(variableInstance, value, sourceActivityExecution);


    }



    public void deleteCascade(String deleteReason) {
        this.deleteReason = deleteReason;
        this.deleteRoot = true;
        performOperation(AtomicOperation.DELETE_CASCADE);
    }

    public int getRevisionNext() {
        return revision + 1;
    }

    public void forceUpdate() {
        this.forcedUpdate = true;
    }


    public String toString() {
        if (isProcessInstanceType()) {
            return "ProcessInstance[" + getProcessDefinitionId() + "]";
        } else {
            return (isConcurrent ? "Concurrent" : "") + (isScope ? "Scope" : "") + "Execution[" + getCurrentActivityName() + "]";
        }
    }

    public String getProcessInstanceId() {
        return processInstanceId;
    }

    public String getParentId() {
        return parentId;
    }

    public void setParentId(String parentId) {
        this.parentId = parentId;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public int getRevision() {
        return revision;
    }

    public void setRevision(int revision) {
        this.revision = revision;
    }

    public String getActivityId() {
        return activityId;
    }

    public TransitionImpl getTransition() {
        return transition;
    }

    public void setTransition(TransitionImpl transition) {
        this.transition = transition;
        if (replacedBy != null) {
            replacedBy.setTransition(transition);
        }
    }

    public TransitionImpl getTransitionBeingTaken() {
        return transitionBeingTaken;
    }

    public void setTransitionBeingTaken(TransitionImpl transitionBeingTaken) {
        this.transitionBeingTaken = transitionBeingTaken;
        if (replacedBy != null) {
            replacedBy.setTransitionBeingTaken(transitionBeingTaken);
        }
    }

    public Integer getExecutionListenerIndex() {
        return executionListenerIndex;
    }

    public void setExecutionListenerIndex(Integer executionListenerIndex) {
        this.executionListenerIndex = executionListenerIndex;
    }

    public boolean isConcurrent() {
        return isConcurrent;
    }

    public void setConcurrent(boolean isConcurrent) {
        this.isConcurrent = isConcurrent;
    }

    public boolean isActive() {
        return isActive;
    }

    public void setActive(boolean isActive) {
        this.isActive = isActive;
    }

    public boolean isEnded() {
        return isEnded;
    }

    public String getEventName() {
        return eventName;
    }

    public void setEventName(String eventName) {
        this.eventName = eventName;
    }

    public PvmProcessElement getEventSource() {
        return eventSource;
    }

    public void setEventSource(PvmProcessElement eventSource) {
        this.eventSource = eventSource;
    }

    public String getDeleteReason() {
        return deleteReason;
    }

    public void setDeleteReason(String deleteReason) {
        this.deleteReason = deleteReason;
    }

    public boolean isDeleteRoot() {
        return deleteRoot;
    }

    public int getSuspensionState() {
        return suspensionState;
    }

    public void setSuspensionState(int suspensionState) {
        this.suspensionState = suspensionState;
    }

    public boolean isSuspended() {
        return suspensionState == SuspensionState.SUSPENDED.getStateCode();
    }

    public boolean isEventScope() {
        return isEventScope;
    }

    public void setEventScope(boolean isEventScope) {
        this.isEventScope = isEventScope;
    }

    public StartingExecution getStartingExecution() {
        return startingExecution;
    }

    public void disposeStartingExecution() {
        startingExecution = null;
    }

    public String getCurrentActivityId() {
        return activityId;
    }

    public String getCurrentActivityName() {
        return activityName;
    }


    public Map<String, Object> getProcessVariables() {
        Map<String, Object> variables = new HashMap<String, Object>();
        if (queryVariables != null) {
            for (VariableInstanceEntity variableInstance : queryVariables) {
                if (variableInstance.getId() != null && variableInstance.getTaskId() == null) {
                    variables.put(variableInstance.getName(), variableInstance.getValue());
                }
            }
        }
        return variables;
    }


}
